#include"tftp_server.h"

//
#define ERR_LOG(msg) do{\
    perror(msg);\
    cout<<__LINE__<<" "<<__func__<<"    "<<__FILE__<<endl;\
}while(0)

//析构函数的定义
TFTPServer::TFTPServer(const string &root):root_dir(root)
{
    //创建UDP
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd<0)
    {
        ERR_LOG("socker  error\n");
        return ;
    }

    //填充地址信息结构体
    server_addr.sin_family =AF_INET;//通信域
    server_addr.sin_port = htons(PORT);//端口号
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    /*服务器 为了“来者不拒”通常用 INADDR_ANY。
    客户端 需要明确指定要连接的服务器 IP（如 inet_pton(AF_INET, "127.0.0.1", &...)），不会使用 INADDR_ANY。*/
    //表示任意一个主机都可以向我发信息

    //设置端口号快速重用
    int reuse = 1;
    if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0)
    {
        ERR_LOG("setsockopt error\n");
        return ;
    }

    //绑定工作
    if(bind(sfd,(struct sockaddr*)&server_addr,sizeof(server_addr))<0)
    {
        ERR_LOG("bind error\n");
        return ;
    }




}

//析构函数的定义
TFTPServer::~TFTPServer()
{
    if(sfd >= 0)
    {
        close(sfd);

    }
}

void TFTPServer::run()
{
    //给出提示信息
    cout<<"TFTP Server started on port:"<<PORT<<endl;
    cout<<"Serving file from: "<<root_dir<<endl;

    char buf[BUFFER_ZISE] = "";//用来存储数据包
    struct sockaddr_in client_addr;//用来存储客户端地址信息结构体
    socklen_t addr_len =sizeof(client_addr);//存储客户端地址信息结构体的大小
    
   //循环跑服务器
   while(1)
   {
    //接收客户端的请求包
    cout<<" 开始接收到了收客户端的请求包\n"<<endl;
    int n = recvfrom(sfd,buf,BUFFER_ZISE,0,(struct sockaddr*)&client_addr,&addr_len);
    cout<<"接收到了收客户端的请求包\n"<<endl;
    if(n<0)
    {
        ERR_LOG("recvfrom error");
        return;
    }

    //TFTP 的操作码是 2 字节（16 bit）无符号整数
    //检查操作码
    if(buf[0] != 0) continue;       //说明协议错误

    char * filename = buf+2;//解析请求文件名
    const char*mode = filename + strlen(filename) +1;//得到协议模式
    //得到协议模式

    //检查传输模式
    if(strcasecmp(mode,"octet") != 0)
    {
        cout<<"是我发出的错误请求"<<endl;
       sendError("Only binary mode supportd", client_addr);
       continue;
    }

    //处理正常请求
    switch(buf[1])
    {
        case 1://读请求
        cout<<"read request for :"<<filename<<endl;

        //调用处理读取请求的函数
        handleReadRequest(filename,client_addr,addr_len);
        break;

        case 2:   //写请求
        cout<<"wirte requset for :"<<filename<<endl;
        //调用处理读请求的函数
        handleWriteRequest(filename,client_addr,addr_len);
        break;

    default:
        sendError("Unknown request",client_addr);
        break;
    }

   }

}

//处理读请求的函数实现
void TFTPServer::handleReadRequest(const char *filename,struct sockaddr_in &client_addr,socklen_t addr_len)
{
    //找到要下载的文件路径
    string full_path = root_dir+'/'+filename; //c++中字符串可以是+拼接
    //以只读的形式打开文件
    int fd = open(full_path.c_str(),O_RDONLY);
    if(fd<0)
    {
        //表示要下载的文件不在服务器
        sendError("FILE not found",client_addr);
        return;
    }

    //c程序执行至此,表示要下载的文件在服务器中存在
    char buf[BUFFER_ZISE] = "";//数据包
    unsigned short block_num = 1;//快编号

    //循环发送数据包
    while(1)
    {
        //准备数据包
        buf[0] = 0;
        buf[1] = 3;

        *(unsigned short *)(buf +2) = htons(block_num); //块编号封装到数据包中

        //从文件中读取数据 buf+4之后的内容
        int n =read(fd,buf+4,BUFFER_ZISE-4);
        if(n<0)
        {
            sendError("Read error",client_addr);
            close(fd);
            return ;
        }

        if(sendto(sfd,buf,n+4,0,(struct sockaddr*)&client_addr,addr_len)<0)
        {
            ERR_LOG("sendto error");
            close(fd);
            return ;
        }

        //程序执行自此 ,表示服务器成功将一个数据包发送给客户端
        if(sendto(sfd,buf,n+4,0,(struct sockaddr*)&client_addr,addr_len)<0)
        {
            ERR_LOG("send error");
            close(fd);
            return ;
        }
        //等待反馈ack
        do{


            //收到客户端发来的ack数据包
            if(recvfrom(sfd,buf,BUFFER_ZISE,0,(struct sockaddr*)&client_addr,&addr_len)<0)
            {
                ERR_LOG("recvfrom error\n");
                close(fd);
                return ;
            }
    //循环等待收ack包
        }while(buf[1] !=4 || ntohs(*(unsigned short *)(buf +2)) != block_num);

        //判断当前的文件是否读取结束
        if(n<BUFFER_ZISE -4)
        {
            break;
        }

        //程序执行至此,表示还有数据等待发送,此时快编号应该自增
        block_num++;

    }

     //关闭文件描述符
     close(fd);
} 


void TFTPServer::handleWriteRequest(const char *filename,struct sockaddr_in &client_addr,socklen_t addr_len)
{
    //找到要上传的文件路径
    string full_path = root_dir +"/"+filename;//c++ 中字符串了可以+拼接
    //以只写的形式打开文件
    int fd = open(full_path.c_str(),O_WRONLY|O_CREAT|O_TRUNC, 0664);
    if(fd<0)
    {
        //表示创建文件失败
        sendError("Cannot create file",client_addr);
        return ;
    }

    //程序执行至此,表示已经创建好文件
    //发送ack 
    char buf[BUFFER_ZISE] = "";     //数据包
    unsigned short block_num = 0;   //快编号

     buf[0] = 0;
    buf[1] = 4;         //ACK包
    *(unsigned short *)(buf+2) = htons(block_num);    //块编号
    //发第一个ack包,确定要开始收了
    if(sendto(sfd,buf,4,0,(struct sockaddr*)&client_addr,addr_len)<0)
    {
        ERR_LOG("sendto error\n");

        return;
    }

    int n = recvfrom(sfd,buf,BUFFER_ZISE,0,(struct sockaddr*)&client_addr,&addr_len);
    //循环接收数据包
    while(1)
    {
        int n = recvfrom(sfd,buf,BUFFER_ZISE,0,(struct sockaddr*)&client_addr,&addr_len);


        //读取数据包
        if(n<0)
        {
            ERR_LOG("recvfrom error\n");
            close(fd);
            return ;
        }

         //判断是否为数据包
         if(buf[1]==3 &&ntohs(*(unsigned short *)(buf +2)) ==block_num +1)
         {
            //说明是数据包,将数据包中的数据,写入到文件中
            if(write(fd,buf+4,n-4)<0)
            {
                sendError("write error",client_addr);
                close(fd);
                return ;
            }
         }

         //向客户端发送一个ACK
         block_num++;
         buf[0] = 0;
         buf[1] = 4;//说明ack包
         *(unsigned short *)(buf +2)= htons(block_num);//快编号

         if(sendto(sfd,buf,4,0,(struct sockaddr*)&client_addr,addr_len)<0)
         {
            ERR_LOG("sendto error");
            return ;
         }

         //判断读取的数据的长度跟缓冲区长度的大小
         if(n<BUFFER_ZISE)
         {
            break;//表示上传完成
         }

    }

    close(fd);//关闭文件


}

//发送错误信息
void TFTPServer::sendError(const char *msg,struct sockaddr_in &client_addr)
{
    //封装错误包
    char buf[BUFFER_ZISE] = "";
    buf[0] = 0;
    buf[1] = 5;        //错误包的操作码   
    buf[2] = 0;
    buf[3] = 1;     //错误码
    strcpy(buf+4,msg);      //错误信息

    //将消息发送给客户端
    //传过去的时候要有反斜杠/0 所以这里要+5
    if(sendto(sfd,buf,strlen(msg)+5,0,(struct sockaddr*)&client_addr,sizeof(client_addr))<0)
    {
        ERR_LOG("sendto error");
        return ;
    }
}

